Weaving maps of multivariate data

Weave patterns where each ‘thread’ or ‘strand’ represents a different attribute that can be independently symbolised in a map.

from Chaves LF, MD Friberg, LA Hurtado, R Marín Rodríguez, D O’Sullivan, and LR Bergmann. 2021 (online first). Trade, uneven development and people in motion: Used territories and the initial spread of COVID-19 in Mesoamerica and the Caribbean. Socio-Economic Planning Sciences.

This was done with SVG symbol fills in QGIS, and was (very!) fiddly to produce.

Motivation and other approaches

Increasingly, we deal with highly multivariate data.

Many approaches can be used to visualise these spatially, but it’s always challenging. We may resort to small multiple displays, where each attribute is presented as an individual (small) map. Or perhaps more often, we flip back and forward among many layers in a GIS.

Wouldn’t it be nice to see multiple attributes together?! Perhaps to be able to identify patterns across more than one attribute in combination.

We’re not the first to think this, so there are plenty of approaches already around…

Small multiples

This is sf’s default plot output for a dataset.

plot(region)

Bivariate choropleths

A crude approach uses alpha values for each attribute, e.g., in tmap.

tm_shape(region) + 
  tm_polygons(col = "dose2_uptake", palette = "-Reds", alpha = 0.65, 
              title = "Dose 2 per thousand ") + 
  tm_shape(region) + 
  tm_polygons(col = "pAsian", palette = "Blues", alpha = 0.35,
              title = "% Asian") +
  tm_layout(legend.outside = TRUE)

Or, use something like Jan Caha’s QGIS plugin which implements the approach described by Joshua Stevens in this post

Bivariate choropleth made in QGIS

Trivariate choropleths

Mixing three colours is hard, but e.g., the tricolore package can do this…

eth_mix <- tricolore::Tricolore(
  region, p1 = "pEuropean", p2 = "pMaori", p3 = "pAsian", breaks = 5
)
region$eth_mix_tri <- eth_mix$rgb

ggplot(region) + 
  geom_sf(aes(fill = eth_mix_tri)) + 
  scale_fill_identity() +
  annotation_custom(
    grob = ggplotGrob(eth_mix$key + labs(L = 'Pākehā', T = 'Māori', R = 'Asian')),
    xmin = 1.7465e6, xmax = 1.7535e6, ymin = 5.9075e6, ymax = 5.9125e6)

Symbols over choropleths

Choropleth with symbols made in QGIS

Multivariate symbols

The classic example is Dorling’s Chernoff faces map of the UK 1987 election.

Figure 8.10 Dorling D. 2012. The visualisation of spatial social structure. Chichester, England: John Wiley & Sons.

Multi-element patterns

There are many variations on this idea, but perhaps the most common is a categorical dot map.

This example made using tmap and data preparation code from James Smythe’s cultureofinsight blog

dot map of ethnic composition

A woven map

weave_unit <- get_biaxial_weave_unit(spacing = 200, type = "twill", n = 3, 
                                     aspect = 0.6, ids = "ab|cd", 
                                     crs = st_crs(region))
fabric <- weave_layer(weave_unit, region, angle = 30)

Split the data by the id so that it is convenient to symbolise them separately.

layers <- fabric %>% split(as.factor(fabric$id))
  • Each id value can be symbolised separately using symbolisation the data can support
  • We can also plot the region data as a choropleth if desired
tm_shape(region, name = "Dose 2 uptake") +
  tm_fill(col = "dose2_uptake", palette = "inferno", style = "cont", 
          title = "Dose 2 per 1000", id = "SA22018_V1_00_NAME") +
  tm_shape(layers$a, name = "Pākehā") +
  tm_fill(col = "pEuropean", palette = "Greys", title = "% Pākehā", n = 3, 
          id = "SA22018_V1_00_NAME") +
  tm_shape(layers$b, name = "Māori") +
  tm_fill(col = "pMaori", palette = "Reds", title = "% Māori", n = 3, 
          id = "SA22018_V1_00_NAME") +
  tm_shape(layers$c, name = "Pasifika") +
  tm_fill(col = "pPacific", palette = "Purples", title = "% Pasifika", n = 3, 
          id = "SA22018_V1_00_NAME") +
  tm_shape(layers$d, name = "Asian") +
  tm_fill(col = "pAsian", palette = "Greens", title = "% Asian", n = 3, 
          id = "SA22018_V1_00_NAME")

Online full screen version

Implementing woven maps

  • Regular rectangular or hex grids of points generated by geospatial tools

tiling a weave unit

What repeatable units can tile across such grids to give the appearance of a woven pattern?

Turns out this is of interest to mathematicians (Grünbaum and Shephard 1985, 1986), who call such tileable elements the fundamental blocks of isonemal fabrics

Anyway… we have proof-of-concept R tools to make weave patterns:

  1. Make a weave unit
  2. Tile the map area with the weave unit
  3. Export to a multi-layer GPKG
  4. Symbolise the weave elements as desired in any tool

Weave units (or fundamental blocks)

Biaxial weaves

Plain weaves

Traditional weave patterns with threads in two directions, the warp and the weft. We generate these using matrix multiplication (cf. Glassner 2002)

Simplest is a plain weave

rect11_unit <-   ## plain weave example
  get_biaxial_weave_unit(spacing = 300, type = "plain",
                    ids = "a|b", crs = st_crs(region))
rect11_unit$primitive %>% plot(border = NA, main = "Plain weave unit")

This could be useful if clearly distinct palettes were used in the warp and weft elements. More useful is if we change the aspect to the warp and weft elements so we can distinguish directions:

rect11_unit <-   ## plain weave example
  get_biaxial_weave_unit(spacing = 300, aspect = 0.8,
                    ids = "a|b", crs = st_crs(region))
rect11_unit$primitive %>% plot(border = NA, main = "Plain weave unit, with directions")

More threads, more colours

This is highly customisable:

rect32_unit <-
  get_biaxial_weave_unit(spacing = 150, aspect = sqrt(0.5), 
                    ids = "abc|de", crs = st_crs(region))
rect32_unit$primitive %>% plot(border = NA, main = "Plain weave, 3 warp and 2 weft colours")

Missing threads

We can even leave gaps or duplicate threads

rect34_unit <-   ## plain weave example
  get_biaxial_weave_unit(spacing = 300, aspect = 0.8,
                    ids = "ab-|cc-d", crs = st_crs(region))
rect34_unit$primitive %>% plot(border = NA, main = "Complex plain weave with missing strands")

Twill weaves and basket weaves

twill_unit <- 
  get_biaxial_weave_unit(spacing = 200, type = "twill", n = c(2, 2), 
                         aspect = 0.7, ids = "a|b", crs = st_crs(region))
twill_unit$primitive %>% plot(border = NA, main = "2 over 2 under twill weave")

basket_unit <- 
  get_biaxial_weave_unit(spacing = 200, type = "basket", n = 2, 
                         aspect = 0.7, ids = "a|b", crs = st_crs(region))
basket_unit$primitive %>% plot(border = NA, main = "2 over 2 under basket weave")

Other weaves

We can generate any biaxial weavable pattern, even crazy ones!

this_unit <- 
  get_biaxial_weave_unit(spacing = 200, type = "this", ids = ids,
                        crs = st_crs(region))
this_unit$primitive %>% plot(border = NA, main = "Pattern from Glassner 2002")

Triaxial weave units

Hexagonal

We can also make weaves with threads running in 3 directions. This example is based on a hexagonal tileable unit, and can also allow for more than one thread in each direction

hex_unit <- ## hex example
  get_triaxial_weave_unit(spacing = 600, margin = 2,
                          ids = "a|bc|def", type = "hex", crs = st_crs(region))
hex_unit$primitive %>% plot(border = NA, main = "Hex-based triangular weave")

A cube is another option, although this produces some odd 3D effects when tiled

cube_unit <-
  get_triaxial_weave_unit(spacing = 600, ids = "ab|cd|ef", margin = 2, 
                          type = "cube", crs = st_crs(region))
cube_unit$primitive %>% plot(border = NA, 
                             main = "Mad weave (so called) with two colours in each direction")

Diamond

An alternative way to produce a triangular weave is with a diamond repeating unit with angles 60° and 120°

diamond_unit <-   ## diamond example
  get_triaxial_weave_unit(spacing = 600, margin = 2,
                          ids = "a|b|c", type = "diamond", crs = st_crs(region))
diamond_unit$primitive %>% plot(border = NA, 
                                main = "Triangular weave with a diamond fundamental block")

Weave a map

tri_unit <-   get_triaxial_weave_unit(spacing = 550, margin = 5, 
                          ids = "a|b|c", type = "hex", crs = st_crs(region))
fabric2 <- weave_layer(tri_unit, region, angle = 45)
layers2 <- fabric2 %>% split(as.factor(fabric2$id))
tmap_mode("view")
tm_shape(region, name = "Dose 2 uptake") +
  tm_fill(col = "dose2_uptake", palette = "-Greys", style = "cont", 
          title = "Dose 2 per 1000") +
  tm_shape(layers2$a, name = "Māori") +
  tm_fill(col = "pMaori", palette = "Reds", title = "% Māori", n = 5) +
  tm_shape(layers2$b, name = "Pasifika") +
  tm_fill(col = "pPacific", palette = "Purples", title = "% Pasifika", n = 5) +
  tm_shape(layers2$c, name = "Asian") +
  tm_fill(col = "pAsian", palette = "Greens", title = "% Asian", n = 5)

Online full screen version

Write the weave layers

We can save out to a multi-layer GPKG for use in any tool

write_weave_layers(fabric, region, "data/fabric.gpkg")
write_weave_layers(fabric2, region, "data/fabric2.gpkg")

Further work

There is lots to do (at least potentially!)

  • Clarify the API for the tools to make their usage clearer
  • Figure out how to make legends…
  • Develop guidelines for what works (and what doesn’t)
  • Figure out how colour work in this setting (how many, what combinations?)
  • Explore what symbolisations work (continuous, classified, categorical?)
  • Understand better how orientation operates
  • Consider the use of ‘gaps’ in a weave pattern

Acknowledgments

References

Glassner A. 2002. Digital weaving 1. IEEE Computer Graphics and Applications 22 (6):108–118.

Glassner A. 2003a. Digital weaving 2. IEEE Computer Graphics and Applications 23 (1):77–90.

Glassner A. 2003b. Digital weaving 3. IEEE Computer Graphics and Applications 23 (2):80–83.

Griswold R. 2006 (unpublished manuscript). Mathematical and Computational Topics in Weaving (last accessed 29 October 2021).

Grünbaum B and GC Shephard. 1985. A catologue of isonemal fabrics. Annals of the New York Academy of Sciences 440 (1 Discrete Geom):279–298.

Grünbaum B and GC Shephard. 1986. An extension to the catalogue of isonemal fabrics. Discrete Mathematics 60:155–192.

And this video by Lea Albaugh provided a nice way in to the topic for us: “It’s Just Matrix Multiplication”: Notation for Weaving, presented at the Strange Loop conference, St Louis, 27-28 Sept, 2018.

Questions?